package com.graduation.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.graduation.common.autoconfigure.mq.handler.MqMessageHelper;
import com.graduation.common.constans.MqConstants;
import com.graduation.common.constans.RedisConstants;
import com.graduation.common.domain.dto.PageDTO;
import com.graduation.common.exceptions.BizException;
import com.graduation.mapper.LegalDocumentMapper;
import com.graduation.pojo.dto.LegalDocumentDTO;
import com.graduation.pojo.po.DocumentTag;
import com.graduation.pojo.po.LegalDocument;
import com.graduation.pojo.po.Tag;
import com.graduation.pojo.query.LegalDocumentQuery;
import com.graduation.pojo.vo.LegalDocumentDetailVO;
import com.graduation.pojo.vo.LegalDocumentVO;
import com.graduation.pojo.vo.TagVO;
import com.graduation.service.DocumentTagService;
import com.graduation.service.LegalDocumentService;
import com.graduation.service.TagService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class LegalDocumentServiceImpl extends ServiceImpl<LegalDocumentMapper, LegalDocument> implements LegalDocumentService {

    private final DocumentTagService documentTagService;

    private final TagService tagService;

    private final MqMessageHelper mqMessageHelper;

    private final StringRedisTemplate stringRedisTemplate;

    @Override
    public PageDTO<LegalDocumentVO> pageLegalDocuments(LegalDocumentQuery query) {
        // 构建查询条件
        LambdaQueryWrapper<LegalDocument> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StrUtil.isNotBlank(query.getTitle()), LegalDocument::getTitle, query.getTitle())
                .eq(query.getDocType() != null, LegalDocument::getDocType, query.getDocType())
                .like(StrUtil.isNotBlank(query.getKeywords()), LegalDocument::getKeywords, query.getKeywords())
                .ge(query.getPublishStartDate() != null, LegalDocument::getPublishDate, query.getPublishStartDate())
                .le(query.getPublishEndDate() != null, LegalDocument::getPublishDate, query.getPublishEndDate())
                .orderByDesc(LegalDocument::getCreateTime);

        // 分页查询
        Page<LegalDocument> page = page(query.toMpPageByCreateTime(), wrapper);

        // 转换为VO并返回PageDTO
        List<LegalDocumentVO> voList = page.getRecords().stream()
                .map(this::convertToVO)
                .collect(Collectors.toList());

        return PageDTO.of(page.getPages(), page.getTotal(), voList);
    }

    @Override
    public LegalDocumentDetailVO getLegalDocumentById(Long id) {
        return getLegalDocumentDetail(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveLegalDocument(LegalDocumentDTO legalDocumentDTO) {
        // 检查标题是否已存在
        boolean exists = lambdaQuery()
                .eq(LegalDocument::getTitle, legalDocumentDTO.getTitle())
                .exists();
        if (exists) {
            throw new BizException("文档标题已存在");
        }

        // 保存文档
        LegalDocument legalDocument = new LegalDocument();
        BeanUtil.copyProperties(legalDocumentDTO, legalDocument);
        save(legalDocument);

        // 保存文档标签关联
        saveLegalDocumentTags(legalDocument.getId(), legalDocumentDTO.getTagIds());

        // 发送mq消息保存到es
        mqMessageHelper.sendMessage(
                MqConstants.EXCHANGE.LIKED_TOPIC_EXCHANGE,
                MqConstants.KEY.LEGAL_DOCUMENT_SAVE_KEY,
                legalDocument
        );
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateLegalDocument(LegalDocumentDTO legalDocumentDTO) {
        LegalDocument existDocument = getById(legalDocumentDTO.getId());
        if (existDocument == null) {
            throw new BizException("法律文档不存在");
        }

        // 检查标题是否被其他文档使用
        if (StrUtil.isNotBlank(legalDocumentDTO.getTitle()) &&
                !legalDocumentDTO.getTitle().equals(existDocument.getTitle())) {
            boolean exists = lambdaQuery()
                    .eq(LegalDocument::getTitle, legalDocumentDTO.getTitle())
                    .ne(LegalDocument::getId, legalDocumentDTO.getId())
                    .exists();
            if (exists) {
                throw new BizException("文档标题已存在");
            }
        }

        // 更新文档
        LegalDocument legalDocument = new LegalDocument();
        BeanUtil.copyProperties(legalDocumentDTO, legalDocument);
        updateById(legalDocument);

        // 删除原有的标签关联
        documentTagService.lambdaUpdate()
                .eq(DocumentTag::getDocumentId, legalDocumentDTO.getId())
                .remove();

        // 保存新的标签关联
        saveLegalDocumentTags(legalDocumentDTO.getId(), legalDocumentDTO.getTagIds());

        // es修改
        mqMessageHelper.sendMessage(
                MqConstants.EXCHANGE.LIKED_TOPIC_EXCHANGE,
                MqConstants.KEY.LEGAL_DOCUMENT_UPDATE_KEY,
                legalDocument
        );
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteLegalDocument(Long id) {
        LegalDocument legalDocument = getById(id);
        if (legalDocument == null) {
            throw new BizException("法律文档不存在");
        }

        // 删除文档标签关联
        documentTagService.lambdaUpdate()
                .eq(DocumentTag::getDocumentId, id)
                .remove();

        // 删除文档
        removeById(id);

        // es删除
        mqMessageHelper.sendMessage(
                MqConstants.EXCHANGE.LIKED_TOPIC_EXCHANGE,
                MqConstants.KEY.LEGAL_DOCUMENT_DELETE_KEY,
                id
        );
    }

    /**
     * 保存文档标签关联
     */
    private void saveLegalDocumentTags(Long documentId, List<Long> tagIds) {
        if (CollUtil.isNotEmpty(tagIds)) {
            // 验证标签是否存在
            List<Tag> existTags = tagService.listByIds(tagIds);
            Set<Long> existTagIds = existTags.stream()
                    .map(Tag::getId)
                    .collect(Collectors.toSet());

            List<Long> invalidTagIds = tagIds.stream()
                    .filter(tagId -> !existTagIds.contains(tagId))
                    .collect(Collectors.toList());

            if (CollUtil.isNotEmpty(invalidTagIds)) {
                throw new BizException("标签不存在：" + invalidTagIds);
            }

            // 保存文档标签关联
            List<DocumentTag> documentTags = tagIds.stream()
                    .map(tagId -> {
                        DocumentTag documentTag = new DocumentTag();
                        documentTag.setDocumentId(documentId);
                        documentTag.setTagId(tagId);
                        return documentTag;
                    })
                    .collect(Collectors.toList());

            documentTagService.saveBatch(documentTags);

            // 更新标签使用次数
            for (Long tagId : tagIds) {
                tagService.lambdaUpdate()
                        .eq(Tag::getId, tagId)
                        .setSql("usage_count = usage_count + 1")
                        .update();
            }
        }
    }

    /**
     * 转换为VO对象（列表用，不包含标签）
     */
    private LegalDocumentVO convertToVO(LegalDocument legalDocument) {
        LegalDocumentVO vo = new LegalDocumentVO();
        BeanUtil.copyProperties(legalDocument, vo);
        return vo;
    }

    /**
     * 前台获取文档详情（通过缓存的形式）
     */
    @Override
    public LegalDocumentDetailVO detail(Long id) {
        return getLegalDocumentDetail(id);
    }

    private LegalDocumentDetailVO getLegalDocumentDetail(Long id) {
        LegalDocument legalDocument = getLegalDocumentCache(id);
        if (legalDocument == null) {
            throw new BizException("法律文档不存在");
        }

        // 转换为详情VO
        LegalDocumentDetailVO detailVO = new LegalDocumentDetailVO();
        BeanUtil.copyProperties(legalDocument, detailVO);

        // 查询关联的标签
        List<Long> tagIds = documentTagService.lambdaQuery()
                .eq(DocumentTag::getDocumentId, id)
                .list()
                .stream()
                .map(DocumentTag::getTagId)
                .collect(Collectors.toList());

        if (CollUtil.isNotEmpty(tagIds)) {
            List<Tag> tags = tagService.listByIds(tagIds);
            List<TagVO> tagVOs = tags.stream()
                    .map(tag -> {
                        TagVO tagVO = new TagVO();
                        BeanUtil.copyProperties(tag, tagVO);
                        return tagVO;
                    })
                    .collect(Collectors.toList());
            detailVO.setTags(tagVOs);
        }

        return detailVO;
    }

    /**
     * 从缓存获取数据（解决了缓存穿透和穿透击穿问题）
     */
    private LegalDocument getLegalDocumentCache(Long id) {
        // 尝试从redis获取
        String key = RedisConstants.LEGAL_CACHE_KEY_PREFIX + id;
        String legalDocumentCache = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(legalDocumentCache)) {
            // 正常数据，直接转换返回
            return JSONUtil.toBean(legalDocumentCache, LegalDocument.class);
        }
        if (legalDocumentCache != null) {
            // 这里代表是脏数据，直接返回null
            return null;
        }

        // 缓存失效了，重建缓存
        int maxRetry = RedisConstants.CACHE_REBUILD_MAX_RETRY;
        while (maxRetry-- > 0) {
            // 二次尝试从redis获取，continue以后其他线程可能已经构建好了数据，所以再获取一次
            key = RedisConstants.LEGAL_CACHE_KEY_PREFIX + id;
            legalDocumentCache = stringRedisTemplate.opsForValue().get(key);
            if (StrUtil.isNotBlank(legalDocumentCache)) {
                // 正常数据，直接转换返回
                return JSONUtil.toBean(legalDocumentCache, LegalDocument.class);
            }
            if (legalDocumentCache != null) {
                // 这里代表是脏数据，直接返回null
                return null;
            }

            // 防止线程安全问题，先获取一下分布式锁，未拿到锁，休息一会儿，再来，拿到锁的重建缓存
            String lockKey = RedisConstants.LEGAL_CACHE_REBUILD_LOCK_KEY;
            try {
                if (!tryLock(lockKey)) {
                    // 未拿到锁，暂停一会儿，然后continue
                    try {
                        Thread.sleep(RedisConstants.CACHE_REBUILD_INTERVAL_TTL);
                        continue;
                    } catch (InterruptedException e) {
                        throw new BizException("获取失败，原因：获取缓存时暂停重建出现意外");
                    }
                }

                // 这里是拿到锁了，重建缓存
                LegalDocument legalDocument = getById(id);
                if (legalDocument == null) {
                    // 代表id是不对的，直接按照缓存穿透的方式处理
                    stringRedisTemplate.opsForValue().set(key, RedisConstants.NULL_VALUE,
                            RedisConstants.NULL_VALUE_TTL, TimeUnit.MINUTES);
                }
                // 到这里代表数据是正确的，写入到缓存就可以了
                stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(legalDocument),
                        RedisConstants.LEGAL_CACHE_TTL, TimeUnit.MINUTES);
                return legalDocument;
            } finally {
                unLock(lockKey);
            }
        }
        // 到这里了，说明重试次数用完了，说明实际是出了问题的，可能是redis卡了，也可能是数据库出问题了
        throw new BizException("缓存构建失败");
    }

    private boolean tryLock(String lockKey) {
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,
                RedisConstants.NULL_VALUE, RedisConstants.LEGAL_CACHE_REBUILD_LOCK_TTL, TimeUnit.MINUTES);
        return BooleanUtil.isTrue(success);
    }

    private void unLock(String lockKey) {
        stringRedisTemplate.delete(lockKey);
    }

}